package emulator.analyzer;

import java.io.PrintStream;
import java.util.HashMap;

import emulator.EmulatorException;
import emulator.analyzer.util.CmpCommandMap;
import emulator.analyzer.util.StatConverter;
import emulator.hardware.HwByte;
import emulator.hardware.HwWord;
import emulator.hardware.memory.CountingMemoryBlock;
import emulator.hardware.memory.MemoryBlockInterface;
import emulator.hardware.nmos6502.Cpu6502;
import emulator.hardware.video.test.CountingClock;
import emulator.util.AddressRange;
import emulator.util.AddressRangeList;

public class Machine {
	private CountingMemoryBlock memory = new CountingMemoryBlock(65536);
	private CountingClock clock = new CountingClock();
	private Cpu6502 cpu = new Cpu6502();
	private CpuExecutionAnalyzer analyzer = null;
	private AddressRange dyn_area;
	private int[] dyn_area_flow;
	
	public Machine() {
		cpu.getIrqIn().setValue(true);
		cpu.getNmiIn().setValue(true);
		cpu.attach(memory, clock);
	}
	
	public void start() {
		cpu.start();
	}

	public MemoryBlockInterface getMemory() {
		return memory;
	}

	public boolean analyzeSubroutine(int address, int accu, int x, int y) {
		analyzer.initRun();
		cpu.setAccu(new HwByte(accu));
		cpu.setX(new HwByte(x));
		cpu.setY(new HwByte(y));
		cpu.setSP(new HwByte(0xff));
		cpu.setPC(address);
		cpu.enableBreakOnReturn(true);
		cpu.go();
		cpu.waitForHalt();
		return analyzer.isOk();
	}

	public int getCycles() {
		return clock.getCounterValue();
	}

	public void dumpMemoryStats(PrintStream out) {
		dumpRanges(out, "Reads:", memory.getReadCounter());
		dumpRanges(out, "Writes:", memory.getWriteCounter());
		out.println("Analyzer stats:");
		analyzer.dumpStats(out, "  ");
		if (dyn_area_flow != null)
		{
			boolean all_ok = true;
			out.println("Dynamic code area "+dyn_area+" stats:");
			for (int i = dyn_area.getStart(); i <= dyn_area.getEnd(); i++)
			{
				if (dyn_area_flow[i] < 0)
				{	out.println(""+new HwWord(i)+": not defined."); all_ok = false; }
				else if (!analyzer.getCodeRange().contains(dyn_area_flow[i]))
				{	out.println(""+new HwWord(i)+": source "+new HwWord(dyn_area_flow[i])+" not defined."); all_ok = false; }
			}
			if (all_ok)
				out.println(" -> All defined!");
		}
	}

	private static void dumpRanges(PrintStream out, String text, int[] counter) {
		out.println(text);
		AddressRangeList range_list = new AddressRangeList(StatConverter.notZero(counter));
		range_list.dump(out, "  ");
	}

	public void init(AddressRange code_range, PrintStream out) {
		cpu.halt();
		try { cpu.reset(); } catch (EmulatorException e) { }
		cpu.step();
		cpu.waitForHalt();
		memory.resetCounter();
		clock.resetCounter();
		analyzer = new CpuExecutionAnalyzer(memory, code_range, out);
		cpu.setExecutionObserver(analyzer);
	}

	public String getTime() {
		double seconds = (double)getCycles() / 1108404.5;
		int min = (int) ((seconds / 60) % 60);
		int hor = (int) (seconds / 3600);
		return String.format("%d:%02d:%04.2f", hor, min, seconds % 60);
	}

	public boolean[] getAccessField() {
		return StatConverter.or(getReadField(), getWriteField());
	}

	private boolean[] getReadField() {
		return StatConverter.notZero(memory.getReadCounter());
	}

	private boolean[] getWriteField() {
		return StatConverter.notZero(memory.getWriteCounter());
	}

	public HashMap<Integer, AddressDefinition> getAddressMap() {
		return analyzer.getAddressMap();
	}

	public boolean activateArea(int execAddress) {
		if (memory.wasWritten(execAddress))
		{
			dyn_area = StatConverter.getRangeAt(getWriteField(), execAddress);
			analyzer.setDynArea(dyn_area);
			dyn_area_flow = analyzer.getAndClearDynAreaFlow();
			return true;
		}
		return false;
	}

	public AddressRange getDynArea() {
		return dyn_area;
	}

	public int[] getDynFlow() {
		return dyn_area_flow;
	}

	public CmpCommandMap getCmpCommands() {
		return analyzer.getCmpCommandMap();
	}
}
